#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ComfyUI 依赖管理工具
"""

import os
import sys
import re
import subprocess
from tempfile import NamedTemporaryFile
import warnings
from typing import List, Dict, Set, Optional
import argparse
import logging
import time
from pathlib import Path
import platform

# 抑制pkg_resources警告
warnings.filterwarnings("ignore", category=UserWarning, module="pkg_resources")

# 直接使用packaging库
from packaging.specifiers import SpecifierSet
from packaging.requirements import Requirement
from packaging.version import parse as parse_version, Version

# 配置日志系统
logging.basicConfig(
    level=logging.INFO,
    format='[%(levelname)s] %(message)s',
    handlers=[
        logging.StreamHandler(sys.stdout),
        logging.FileHandler('dependency_install.log')
    ]
)
logger = logging.getLogger(__name__)

class DependencyManager:
    def __init__(self):
        self.installed_packages = self.get_installed_packages()
    
    @staticmethod
    def sanitize_requirement(line: str) -> Optional[str]:
        """
        清理和验证requirements.txt中的单行
        返回处理后的有效依赖字符串或None
        """
        line = line.strip()
        
        # 跳过空行和注释
        if not line or line.startswith(('#', '-', '--')):
            return None
        
        # 移除行内注释
        line = re.sub(r'\s*#.*$', '', line)
        
        # 验证基本格式
        if not re.match(r'^[a-zA-Z0-9_.-]+(\[[a-zA-Z0-9_,.-]+\])?(?:[><=!~]=?[\d\w\.]+)?(?:,\s*[><=!~]=?[\d\w\.]+)*$', line):
            logger.warning(f"忽略格式不正确的依赖项: {line}")
            return None
            
        return line

    def get_requirements_from_path(self, path: str) -> List[dict]:
        """
        从传入路径的当前目录和一级子目录中收集 requirements.txt 文件
        返回每个依赖的字典，包含req_obj、文件路径、行号、原始行
        """
        requirements = []

        if not os.path.exists(path):
            logger.error(f"路径不存在: {path}")
            return requirements

        # 检查当前目录下的requirements.txt
        req_file_root = os.path.join(path, 'requirements.txt')
        if os.path.isfile(req_file_root):
            try:
                with open(req_file_root, 'r', encoding='utf-8') as f:
                    for line_num, line in enumerate(f, 1):
                        cleaned = self.sanitize_requirement(line)
                        if cleaned:
                            try:
                                req_obj = Requirement(cleaned)
                                requirements.append({
                                    'req_obj': req_obj,
                                    'file_path': req_file_root,
                                    'line_number': line_num,
                                    'original_line': line.strip()
                                })
                            except Exception:
                                continue
                logger.info(f"已处理: {req_file_root}")
            except Exception as e:
                logger.error(f"读取文件失败 {req_file_root}: {str(e)}")

        # 遍历一级子目录
        for name in os.listdir(path):
            subdir = os.path.join(path, name)
            if os.path.isdir(subdir):
                req_file = os.path.join(subdir, 'requirements.txt')
                if os.path.isfile(req_file):
                    try:
                        with open(req_file, 'r', encoding='utf-8') as f:
                            for line_num, line in enumerate(f, 1):
                                cleaned = self.sanitize_requirement(line)
                                if cleaned:
                                    try:
                                        req_obj = Requirement(cleaned)
                                        requirements.append({
                                            'req_obj': req_obj,
                                            'file_path': req_file,
                                            'line_number': line_num,
                                            'original_line': line.strip()
                                        })
                                    except Exception:
                                        continue
                        logger.info(f"已处理: {req_file}")
                    except Exception as e:
                        logger.error(f"读取文件失败 {req_file}: {str(e)}")

        return requirements

    @staticmethod
    def get_installed_packages() -> Dict[str, str]:
        """获取当前环境中已安装的所有包及其版本"""
        import pkg_resources
        return {pkg.key: pkg.version for pkg in pkg_resources.working_set}

    def resolve_dependencies(self, all_requirements: List[dict]) -> tuple[Dict[str, str], Dict[str, Dict]]:
        """
        解析依赖关系并解决冲突，all_requirements为带字段的依赖字典列表
        返回: (无冲突的包字典, 冲突包信息字典)
        """
        to_install = {}
        conflicts = {}
        
        # 检查 onnxruntime 和 onnxruntime-gpu 的选择逻辑
        onnx_packages = [item for item in all_requirements if item['req_obj'].name in ['onnxruntime', 'onnxruntime-gpu']]
        if len(onnx_packages) >= 2:
            logger.info("发现同时存在 'onnxruntime' 和 'onnxruntime-gpu'，正在检测GPU...")
            
            # 检测是否有独显
            has_dedicated_gpu = self.check_dedicated_gpu()
            
            if has_dedicated_gpu:
                logger.info("检测到独显，将使用 'onnxruntime-gpu'")
                # 移除 onnxruntime，保留 onnxruntime-gpu
                all_requirements = [item for item in all_requirements if item['req_obj'].name != 'onnxruntime']
                
                # 如果 onnxruntime 已经安装，先卸载它
                if 'onnxruntime' in self.installed_packages:
                    logger.info("卸载已安装的 'onnxruntime'...")
                    try:
                        subprocess.run(
                            [sys.executable, '-m', 'pip', 'uninstall', '-y', 'onnxruntime'],
                            check=True,
                            stderr=subprocess.PIPE,
                            text=True
                        )
                        logger.info("成功卸载 'onnxruntime'")
                    except subprocess.CalledProcessError as e:
                        logger.error(f"卸载 'onnxruntime' 失败: {e.stderr}")
            else:
                logger.info("未检测到独显，将使用 'onnxruntime'")
                # 移除 onnxruntime-gpu，保留 onnxruntime
                all_requirements = [item for item in all_requirements if item['req_obj'].name != 'onnxruntime-gpu']
                
                # 如果 onnxruntime-gpu 已经安装，先卸载它
                if 'onnxruntime-gpu' in self.installed_packages:
                    logger.info("卸载已安装的 'onnxruntime-gpu'...")
                    try:
                        subprocess.run(
                            [sys.executable, '-m', 'pip', 'uninstall', '-y', 'onnxruntime-gpu'],
                            check=True,
                            stderr=subprocess.PIPE,
                            text=True
                        )
                        logger.info("成功卸载 'onnxruntime-gpu'")
                    except subprocess.CalledProcessError as e:
                        logger.error(f"卸载 'onnxruntime-gpu' 失败: {e.stderr}")
        
        # 分组
        package_requirements = {}
        for item in all_requirements:
            pkg_name = item['req_obj'].name
            package_requirements.setdefault(pkg_name, []).append(item)

        logger.info(f"📊 解析后的包分组总数: {len(package_requirements)}")
        logger.info(f"📊 原始requirements总数: {len(all_requirements)}")
        
        # 调试：显示已安装的包名（仅显示包含下划线或连字符的包）
        debug_packages = {name: version for name, version in self.installed_packages.items() 
                         if '_' in name or '-' in name}
        if debug_packages:
            logger.debug(f"🔍 已安装的包名示例（包含下划线或连字符）: {debug_packages}")
        
        # 处理每个包的版本要求
        for pkg_name, req_items in package_requirements.items():
            try:
                # 合并所有版本约束
                constraint_strings = [str(spec) for item in req_items for spec in item['req_obj'].specifier]
                
                # 尝试找出最大包容范围
                merged_spec = self._find_maximum_compatible_range(constraint_strings)
                
                if merged_spec is None:
                    conflicts[pkg_name] = {
                        'requirements': [item['original_line'] for item in req_items],
                        'suggestions': self.generate_conflict_suggestions([item['req_obj'].specifier for item in req_items])
                    }
                    logger.warning(f"发现版本冲突: {pkg_name} - {conflicts[pkg_name]['requirements']}")
                    continue
                
                # 检查是否已安装且满足要求
                pkg_name_lower = pkg_name.lower()
                
                # 标准化包名：去掉下划线和连字符，转小写
                pkg_name_normalized = pkg_name_lower.replace('_', '').replace('-', '')
                
                installed_pkg_name = None
                # 遍历已安装的包，对每个包名进行标准化处理
                for installed_name in self.installed_packages.keys():
                    installed_name_normalized = installed_name.lower().replace('_', '').replace('-', '')
                    if pkg_name_normalized == installed_name_normalized:
                        installed_pkg_name = installed_name
                        break
                
                if installed_pkg_name:
                    installed_version = parse_version(self.installed_packages[installed_pkg_name])
                    
                    if merged_spec.contains(installed_version):
                        logger.info(f"✅ 已满足: {pkg_name} {self.installed_packages[installed_pkg_name]} (满足要求: {merged_spec})")
                        continue
                    else:
                        logger.info(f"🔄 版本不匹配: {pkg_name} 已安装 {self.installed_packages[installed_pkg_name]}, 需要 {merged_spec}")
                else:
                    # 调试：显示标准化后的包名比较
                    logger.debug(f"🔍 包名检查: {pkg_name} -> 标准化后: {pkg_name_normalized}")
                    logger.debug(f"🔍 已安装包标准化后: {[(name, name.lower().replace('_', '').replace('-', '')) for name in self.installed_packages.keys()]}")
                    logger.info(f"📦 需要安装: {pkg_name} {merged_spec}")
                
                # 添加到安装列表
                to_install[pkg_name] = str(merged_spec)
                
            except Exception as e:
                logger.error(f"处理包 {pkg_name} 失败: {str(e)}")
                continue

        logger.info(f"🔍 调试信息: 安装的包数量={len(to_install) if to_install else 0}, 冲突包数量={len(conflicts) if conflicts else 0}")
        
        # 打印详细的安装包来源和版本要求
        if to_install:
            logger.info(f"📦 需要安装的包总数: {len(to_install)}")
            logger.info("📋 安装包详细列表:")
            for i, (pkg, version) in enumerate(to_install.items(), 1):
                logger.info(f"  {i}. {pkg}{version if version else ''}")
                if pkg in package_requirements:
                    for source in package_requirements[pkg]:
                        logger.info(f"      来源: {source['file_path']}:{source['line_number']}  版本要求: {source['req_obj'].specifier}  原始: {source['original_line']}")
        else:
            logger.info("✅ 所有依赖包都已满足要求，无需安装")
        
        if conflicts:
            logger.warning(f"⚠️  发现冲突的包总数: {len(conflicts)}")
            logger.warning("🔴 冲突包列表:")
            for i, (pkg_name, conflict_info) in enumerate(conflicts.items(), 1):
                logger.warning(f"  {i:2d}. {pkg_name} - 冲突要求: {', '.join(conflict_info['requirements'])}")
        
        return to_install, conflicts

    def merge_version_specifiers(self, req_objs: List) -> tuple[SpecifierSet, bool]:
        """
        智能合并同一包的多个版本要求
        返回: (合并后的SpecifierSet, 是否成功)
        """
        if not req_objs:
            return SpecifierSet(""), True
        
        if len(req_objs) == 1:
            return req_objs[0].specifier, True
        
        # 收集所有约束
        all_constraints = []
        for req in req_objs:
            all_constraints.extend(req.specifier)
        
        try:
            # 直接合并所有约束
            constraint_strings = [str(c) for c in all_constraints]
            merged_spec = SpecifierSet(",".join(constraint_strings))
            
            logger.debug(f"成功合并约束: {merged_spec}")
            return merged_spec, True
                
        except Exception as e:
            logger.warning(f"合并约束失败: {e}")
            # 合并失败时，选择最宽松的约束
            fallback_spec = self.select_loosest_constraint(constraint_strings)
            return SpecifierSet(fallback_spec), False

    def select_loosest_constraint(self, constraint_strings: List[str]) -> str:
        """从多个约束中选择最宽松的一个"""
        if not constraint_strings:
            return ""
        
        if len(constraint_strings) == 1:
            return constraint_strings[0]
        
        # 过滤掉空字符串
        valid_constraints = [c for c in constraint_strings if c.strip()]
        if not valid_constraints:
            return ""
        
        if len(valid_constraints) == 1:
            return valid_constraints[0]
        
        # 优先选择 >= 约束（最宽松）
        for constraint in valid_constraints:
            if constraint.startswith('>='):
                logger.info(f"选择最宽松约束: >={constraint[2:]}")
                return constraint
        
        # 其次选择 ~= 约束（兼容版本）
        for constraint in valid_constraints:
            if constraint.startswith('~='):
                logger.info(f"选择兼容约束: ~={constraint[2:]}")
                return constraint
        
        # 最后选择 == 约束（固定版本）
        for constraint in valid_constraints:
            if constraint.startswith('=='):
                logger.info(f"选择固定版本: =={constraint[2:]}")
                return constraint
        
        # 如果都没有匹配，选择第一个
        logger.info(f"选择第一个约束: {valid_constraints[0]}")
        return valid_constraints[0]

    def generate_conflict_suggestions(self, spec_sets: List[SpecifierSet]) -> List[str]:
        """为冲突的包生成解决建议"""
        suggestions = []
        
        # 收集所有版本约束字符串
        constraint_strings = []
        for spec_set in spec_sets:
            for spec in spec_set:
                constraint_strings.append(str(spec))
        
        # 建议1：选择最宽松的约束
        loosest = self.select_loosest_constraint(constraint_strings)
        suggestions.append(f"最宽松约束: {loosest}")
        
        # 建议2：选择最新的稳定版本
        suggestions.append("最新稳定版本: 移除版本约束，让pip选择最新版本")
        
        # 建议3：选择固定版本（如果有的话）
        fixed_versions = [v for v in constraint_strings if '==' in v]
        if fixed_versions:
            suggestions.append(f"固定版本选项: {', '.join(fixed_versions)}")
        
        return suggestions

    def check_dedicated_gpu(self) -> bool:
        """
        检测是否有独显
        返回 True 如果有独显，False 如果只有集成显卡
        """
        try:
            import subprocess
            import re
            
            # 使用 nvidia-smi 检测 NVIDIA GPU
            try:
                result = subprocess.run(['nvidia-smi'], capture_output=True, text=True, timeout=10)
                if result.returncode == 0:
                    logger.info("检测到 NVIDIA GPU")
                    return True
            except (subprocess.TimeoutExpired, FileNotFoundError):
                pass
            
            # 使用 wmic 检测所有显卡
            try:
                result = subprocess.run(
                    ['wmic', 'path', 'win32_VideoController', 'get', 'name,AdapterRAM'],
                    capture_output=True, text=True, timeout=10
                )
                
                if result.returncode == 0:
                    lines = result.stdout.strip().split('\n')
                    for line in lines[1:]:  # 跳过标题行
                        if line.strip():
                            # 检查是否是独显（通常有显存且不是集成显卡）
                            if ('NVIDIA' in line.upper() or 
                                'AMD' in line.upper() or 
                                'RADEON' in line.upper() or
                                'RTX' in line.upper() or
                                'GTX' in line.upper()):
                                logger.info(f"检测到独显: {line.strip()}")
                                return True
                            
                            # 检查显存大小（独显通常有较大显存）
                            if 'AdapterRAM' in line and line.strip():
                                try:
                                    ram_str = re.search(r'(\d+)', line)
                                    if ram_str:
                                        ram_mb = int(ram_str.group(1)) / (1024 * 1024)
                                        if ram_mb > 1024:  # 大于1GB可能是独显
                                            logger.info(f"检测到可能的大显存显卡: {ram_mb:.0f}MB")
                                            return True
                                except:
                                    pass
            except (subprocess.TimeoutExpired, FileNotFoundError):
                pass
            
            logger.info("未检测到独显，将使用CPU版本")
            return False
            
        except Exception as e:
            logger.warning(f"GPU检测失败: {str(e)}，默认使用CPU版本")
            return False

    def install_packages(self, packages: Dict[str, str], conflicts: Dict[str, Dict] = None, search_paths: List[str] = None, verbose: bool = False) -> bool:
        """
        分阶段安装指定的包集合
        返回安装是否成功
        """
        if not packages and not conflicts:
            logger.info("✅ 没有需要安装的依赖包")
            return True
            
        logger.info(f"🔍 调试信息: packages数量={len(packages) if packages else 0}, conflicts数量={len(conflicts) if conflicts else 0}")
        
        overall_success = True
        
        # 第1阶段：安装无冲突的包
        if packages:
            logger.info(f"第1阶段：安装 {len(packages)} 个无冲突的依赖包...")
            stage1_success = self._install_package_batch_fast(packages, search_paths, verbose)
            logger.info(f"🔍 调试信息: 第1阶段安装结果 = {stage1_success}")
            if not stage1_success:
                logger.warning("⚠️  第1阶段：部分无冲突依赖包安装失败，但将继续处理冲突包")
                overall_success = False
            else:
                logger.info("✅ 第1阶段：无冲突依赖包安装完成")
        else:
            logger.info("✅ 第1阶段：无需安装无冲突的依赖包")
        
        # 第2阶段：处理冲突的包
        if conflicts:
            logger.info(f"第2阶段：处理 {len(conflicts)} 个版本冲突的依赖包...")
            logger.info(f"🔍 调试信息: 冲突包列表 = {list(conflicts.keys())}")
            resolved = self.resolve_version_conflicts_interactive(conflicts)
            logger.info(f"🔍 调试信息: 解决后的包数量 = {len(resolved)}")
            if resolved:
                stage2_success = self._install_package_batch_fast(resolved, search_paths, verbose)
                logger.info(f"🔍 调试信息: 第2阶段安装结果 = {stage2_success}")
                if not stage2_success:
                    logger.error("❌ 第2阶段：冲突依赖包安装失败")
                    overall_success = False
                else:
                    logger.info("✅ 第2阶段：冲突依赖包安装完成")
            else:
                logger.warning("⚠️  用户取消了冲突包的安装")
        else:
            logger.info("✅ 第2阶段：没有冲突包需要处理")
        
        # 检查安装后的依赖冲突
        logger.info("\n🔍 检查依赖冲突...")
        self._check_dependency_conflicts()
        
        if overall_success:
            logger.info("✅ 所有依赖包安装完成")
        else:
            logger.warning("⚠️  部分依赖包安装失败，请检查上述错误信息")
        
        return overall_success

    def _install_package_batch(self, packages: Dict[str, str], search_paths: List[str] = None, verbose: bool = False) -> bool:
        """安装一批包"""
        if not packages:
            return True
        
        # 打印要安装的库列表
        logger.info(f"📦 将要安装 {len(packages)} 个依赖包：")
        for i, (pkg_name, version_spec) in enumerate(packages.items(), 1):
            if version_spec and version_spec.strip():
                if any(op in version_spec for op in ['==', '>=', '<=', '!=', '~=']):
                    logger.info(f"  {i}. {pkg_name}{version_spec}")
                else:
                    logger.info(f"  {i}. {pkg_name}{version_spec}")
            else:
                logger.info(f"  {i}. {pkg_name}")
        logger.info("")  # 空行分隔
        
        # 逐个安装包，这样可以看到每个包的安装过程
        success_count = 0
        failed_packages = []
        
        for i, (pkg_name, version_spec) in enumerate(packages.items(), 1):
            # 构建完整的包名+版本信息
            if version_spec and version_spec.strip():
                # 检查version_spec是否已经包含包名
                if version_spec.startswith(pkg_name):
                    # 如果version_spec已经包含包名，直接使用
                    full_pkg_name = version_spec
                else:
                    # 否则拼接包名和版本约束
                    full_pkg_name = f"{pkg_name}{version_spec}"
            else:
                full_pkg_name = pkg_name
            
            logger.info(f"🔧 [{i}/{len(packages)}] 正在安装: {full_pkg_name}")
            
            # 显示该包在哪些requirements文件中定义
            if search_paths:
                sources = self.find_package_in_requirements(pkg_name, search_paths)
                if sources:
                    logger.info(f"   📁 来源文件:")
                    for source in sources:
                        logger.info(f"      - {source['file_path']}:{source['line_number']} ({source['original_line']})")
                else:
                    logger.warning(f"   ⚠️  未找到包 '{pkg_name}' 在requirements文件中的定义")
            
            try:
                if verbose:
                    # 非静默安装，显示实时输出
                    logger.info(f"   📥 开始下载和安装...")
                    result = subprocess.run(
                        [sys.executable, '-m', 'pip', 'install', '-U', full_pkg_name],
                        check=True,
                        text=True
                    )
                else:
                    # 静默安装，只捕获错误
                    result = subprocess.run(
                        [sys.executable, '-m', 'pip', 'install', '-U', full_pkg_name],
                        check=True,
                        stderr=subprocess.PIPE,
                        stdout=subprocess.PIPE,
                        text=True
                    )
                    
                # 过滤掉PATH相关的警告信息
                if result.stderr:
                    filtered_stderr = []
                    for line in result.stderr.split('\n'):
                        if not any(warning in line.lower() for warning in [
                            'which is not on path',
                            'consider adding this directory to path',
                            'use --no-warn-script-location'
                        ]):
                            filtered_stderr.append(line)
                    if filtered_stderr:
                        logger.debug("安装输出: " + '\n'.join(filtered_stderr))
                
                logger.info(f"✅ [{i}/{len(packages)}] {full_pkg_name} 安装成功")
                success_count += 1

            except subprocess.CalledProcessError as e:
                logger.error(f"❌ [{i}/{len(packages)}] {full_pkg_name} 安装失败")
                if not verbose and e.stderr:
                    # 过滤掉PATH相关的警告信息
                    filtered_stderr = []
                    for line in e.stderr.split('\n'):
                        if not any(warning in line.lower() for warning in [
                            'which is not on path',
                            'consider adding this directory to path',
                            'use --no-warn-script-location'
                        ]):
                            filtered_stderr.append(line)
                    if filtered_stderr:
                        logger.error(f"错误信息: {chr(10).join(filtered_stderr)}")
                
                # 针对特定包提供安装建议
                if pkg_name.lower() == 'psd-tools':
                    logger.info("💡 psd-tools 安装建议:")
                    logger.info("   1. 确保已安装 Visual Studio Build Tools")
                    logger.info("   2. 尝试: pip install --upgrade setuptools wheel")
                    logger.info("   3. 或者使用预编译版本: pip install psd-tools --only-binary=all")
                elif pkg_name.lower() in ['pillow', 'pil']:
                    logger.info("💡 Pillow 安装建议:")
                    logger.info("   1. 尝试: pip install --upgrade pip setuptools wheel")
                    logger.info("   2. 或者使用预编译版本: pip install pillow --only-binary=all")
                
                failed_packages.append(full_pkg_name)
        
        # 显示安装结果
        logger.info(f"\n📊 安装结果: {success_count}/{len(packages)} 个包成功安装")
        if failed_packages:
            logger.error(f"❌ 失败的包: {', '.join(failed_packages)}")
            logger.warning(f"⚠️  有 {len(failed_packages)} 个包安装失败，但其他 {success_count} 个包安装成功")
            # 即使有失败的包，也返回True，让上层函数决定如何处理
            return success_count > 0  # 只要有成功的包就认为这阶段基本成功
        else:
            logger.info("✅ 所有依赖包安装成功")
            
            # 检查安装后的依赖冲突
            logger.info("\n🔍 检查依赖冲突...")
            self._check_dependency_conflicts()
            
            return True

    def _install_package_batch_fast(self, packages: Dict[str, str], search_paths: List[str] = None, verbose: bool = False) -> bool:
        """快速批量安装包，使用requirements文件批量安装"""
        if not packages:
            return True
        
        # 创建临时requirements文件进行批量安装
        with NamedTemporaryFile(mode='w+', suffix='.txt', delete=False) as tmp_file:
            for pkg_name, version_spec in packages.items():
                # 构建完整的包名+版本信息
                if version_spec and version_spec.strip():
                    # 检查version_spec是否已经包含包名
                    if version_spec.startswith(pkg_name):
                        # 如果version_spec已经包含包名，直接使用
                        full_pkg_name = version_spec
                    else:
                        # 否则拼接包名和版本约束
                        full_pkg_name = f"{pkg_name}{version_spec}"
                else:
                    full_pkg_name = pkg_name
                
                tmp_file.write(f"{full_pkg_name}\n")
            
            tmp_file.flush()
            tmp_file_path = tmp_file.name
        
        try:
            logger.info(f"🚀 开始快速批量安装 {len(packages)} 个包...")
            
            if verbose:
                # 非静默安装，显示实时输出
                result = subprocess.run(
                    [sys.executable, '-m', 'pip', 'install', '-U', '-r', tmp_file_path],
                    check=True,
                    text=True
                )
            else:
                # 静默安装，只捕获错误
                result = subprocess.run(
                    [sys.executable, '-m', 'pip', 'install', '-U', '-r', tmp_file_path],
                    check=True,
                    stderr=subprocess.PIPE,
                    stdout=subprocess.PIPE,
                    text=True
                )
                
            # 过滤掉PATH相关的警告信息
            if result.stderr:
                filtered_stderr = []
                for line in result.stderr.split('\n'):
                    if not any(warning in line.lower() for warning in [
                        'which is not on path',
                        'consider adding this directory to path',
                        'use --no-warn-script-location'
                    ]):
                        filtered_stderr.append(line)
                if filtered_stderr:
                    logger.debug("安装输出: " + '\n'.join(filtered_stderr))
            
            logger.info(f"✅ 快速批量安装成功，共安装 {len(packages)} 个包")
            
            # 检查安装后的依赖冲突
            logger.info("\n🔍 检查依赖冲突...")
            self._check_dependency_conflicts()
            
            return True

        except subprocess.CalledProcessError as e:
            logger.error(f"❌ 快速批量安装失败")
            if not verbose and e.stderr:
                # 过滤掉PATH相关的警告信息
                filtered_stderr = []
                for line in e.stderr.split('\n'):
                    if not any(warning in line.lower() for warning in [
                        'which is not on path',
                        'consider adding this directory to path',
                        'use --no-warn-script-location'
                    ]):
                        filtered_stderr.append(line)
                if filtered_stderr:
                    logger.error(f"错误信息: {chr(10).join(filtered_stderr)}")
            
            # 询问用户是否要逐个安装
            logger.warning("⚠️  快速批量安装失败")
            logger.info("💡 建议：")
            logger.info("   - 快速批量安装能确保版本一致性")
            logger.info("   - 逐个安装可能导致版本冲突或覆盖")
            logger.info("   - 建议先检查错误信息，修复后重试快速安装")
            
            try:
                while True:
                    choice = input("\n❓ 是否要回退到逐个安装？(y/N): ").strip().lower()
                    if choice in ['y', 'yes']:
                        logger.warning("⚠️  用户选择逐个安装，可能存在版本冲突风险")
                        return self._install_package_batch(packages, search_paths, verbose)
                    elif choice in ['n', 'no', '']:
                        logger.info("❌ 用户取消安装")
                        return False
                    else:
                        print("请输入 y 或 n")
            except KeyboardInterrupt:
                logger.info("❌ 用户取消安装")
                return False
            
        finally:
            # 清理临时文件
            try:
                os.unlink(tmp_file_path)
            except:
                pass

    def rollback_installation(self):
        """回滚到安装前的状态"""
        logger.warning("正在回滚安装...")
        
        with NamedTemporaryFile(mode='w+', suffix='.txt') as tmp_file:
            for pkg, version in self.installed_packages.items():
                tmp_file.write(f"{pkg}=={version}\n")
            tmp_file.flush()
            
            try:
                subprocess.run(
                    ['pip-sync', tmp_file.name],
                    check=True,
                )
                logger.info("回滚完成")
                
            except subprocess.CalledProcessError as e:
                logger.critical(f"回滚失败: {e.stderr}")
                raise RuntimeError("无法回滚安装状态")

    def save_snapshot(self, filename="requirements_snapshot.txt"):
        """保存当前依赖快照（使用 pip freeze）"""
        try:
            with open(filename, 'w', encoding='utf-8') as f:
                subprocess.run(
                    [sys.executable, '-m', 'pip', 'freeze'],
                    stdout=f,
                    check=True
                )
            logger.info(f"已保存当前依赖快照到 {filename}")
        except Exception as e:
            logger.error(f"保存依赖快照失败: {str(e)}")

    def restore_snapshot(self, filename="requirements_snapshot.txt"):
        """还原 pip freeze 快照"""
        if not os.path.exists(filename):
            logger.error(f"快照文件不存在: {filename}")
            return False
        logger.warning(f"将从 {filename} 恢复依赖...")
        try:
            subprocess.run(
                ['pip-sync', filename],
                check=True
            )
            logger.info("已成功恢复依赖快照")
            return True
        except subprocess.CalledProcessError as e:
            logger.error(f"恢复依赖失败: {e.stderr}")
            return False

    def find_package_in_requirements(self, package_name: str, search_paths: List[str]) -> List[Dict[str, str]]:
        """
        查找指定包在哪些requirements文件中定义
        返回包含文件路径和版本信息的列表
        """
        results = []
        package_name_lower = package_name.lower()
        
        for path in search_paths:
            path_obj = Path(path)
            if not path_obj.exists():
                continue
                
            # 查找当前目录和一级子目录下的requirements文件
            requirements_files = []
            if path_obj.is_file() and path_obj.name.lower() in ['requirements.txt', 'requirements-dev.txt']:
                requirements_files = [path_obj]
            elif path_obj.is_dir():
                # 查找当前目录下的requirements文件
                current_dir_files = [
                    f for f in path_obj.iterdir() 
                    if f.is_file() and f.name.lower().startswith('requirements') and f.name.lower().endswith('.txt')
                ]
                requirements_files.extend(current_dir_files)
                
                # 查找一级子目录下的requirements文件
                for subdir in path_obj.iterdir():
                    if subdir.is_dir():
                        subdir_files = [
                            f for f in subdir.iterdir() 
                            if f.is_file() and f.name.lower().startswith('requirements') and f.name.lower().endswith('.txt')
                        ]
                        requirements_files.extend(subdir_files)
            
            for req_file in requirements_files:
                try:
                    with open(req_file, 'r', encoding='utf-8') as f:
                        for line_num, line in enumerate(f, 1):
                            line = line.strip()
                            if not line or line.startswith('#'):
                                continue
                                
                            # 解析包名
                            req = self.sanitize_requirement(line)
                            if not req:
                                continue
                                
                            try:
                                req_obj = Requirement(req)
                                if req_obj.name.lower() == package_name_lower:
                                    results.append({
                                        'file_path': str(req_file),
                                        'line_number': line_num,
                                        'package_name': req_obj.name,
                                        'version_spec': str(req_obj.specifier) if req_obj.specifier else '',
                                        'original_line': line
                                    })
                            except Exception:
                                # 如果解析失败，尝试简单的字符串匹配
                                if package_name_lower in line.lower():
                                    results.append({
                                        'file_path': str(req_file),
                                        'line_number': line_num,
                                        'package_name': package_name,
                                        'version_spec': '',
                                        'original_line': line
                                    })
                except Exception as e:
                    logger.warning(f"读取文件 {req_file} 失败: {e}")
        
        return results

    def _check_dependency_conflicts(self):
        """检查并报告依赖冲突"""
        try:
            # 使用pip check检查依赖冲突
            result = subprocess.run(
                [sys.executable, '-m', 'pip', 'check'],
                capture_output=True,
                text=True,
                timeout=30
            )
            
            if result.returncode != 0:
                logger.warning("⚠️  检测到依赖冲突:")
                logger.warning(result.stdout)
                if result.stderr:
                    logger.warning(result.stderr)
                
                # 提供解决建议
                logger.info("\n💡 解决建议:")
                logger.info("1. 运行 'pip install --upgrade pip' 更新pip")
                logger.info("2. 运行 'pip check' 查看详细冲突信息")
                logger.info("3. 手动安装缺失的依赖包")
                logger.info("4. 考虑使用虚拟环境隔离依赖")
            else:
                logger.info("✅ 未检测到依赖冲突")
                
        except subprocess.TimeoutExpired:
            logger.warning("⚠️  依赖冲突检查超时")
        except Exception as e:
            logger.warning(f"⚠️  依赖冲突检查失败: {e}")

    def _check_and_suggest_path_config(self):
        """检查Python Scripts目录是否在PATH中，并提供配置建议"""
        try:
            # 获取Python Scripts目录
            scripts_dir = Path(sys.executable).parent / "Scripts"
            if not scripts_dir.exists():
                scripts_dir = Path(sys.executable).parent / "bin"  # Unix系统
            
            # 检查是否在PATH中
            import os
            path_dirs = os.environ.get('PATH', '').split(os.pathsep)
            scripts_in_path = str(scripts_dir) in path_dirs
            
            if not scripts_in_path:
                logger.info("\n📋 PATH配置建议:")
                logger.info(f"Python Scripts目录 '{scripts_dir}' 不在系统PATH中")
                logger.info("这可能导致某些命令行工具无法直接使用")
                logger.info("\n💡 解决方法:")
                
                if platform.system() == "Windows":
                    logger.info("1. 临时添加到PATH（当前会话）:")
                    logger.info(f"   set PATH=%PATH%;{scripts_dir}")
                    logger.info("2. 永久添加到PATH:")
                    logger.info("   - 右键'此电脑' -> 属性 -> 高级系统设置")
                    logger.info("   - 环境变量 -> 系统变量 -> Path -> 编辑")
                    logger.info(f"   - 添加: {scripts_dir}")
                else:
                    logger.info("1. 临时添加到PATH（当前会话）:")
                    logger.info(f"   export PATH=$PATH:{scripts_dir}")
                    logger.info("2. 永久添加到PATH:")
                    logger.info(f"   echo 'export PATH=$PATH:{scripts_dir}' >> ~/.bashrc")
                    logger.info("   source ~/.bashrc")
                
                logger.info("\n3. 或者使用完整路径运行工具:")
                logger.info(f"   {scripts_dir}\\tool_name.exe")
                
            else:
                logger.debug("✅ Python Scripts目录已在PATH中")
                
        except Exception as e:
            logger.warning(f"PATH检查失败: {e}")

    def _find_maximum_compatible_range(self, constraint_strings: List[str]) -> Optional[SpecifierSet]:
        """尝试找出最大包容范围，如果找不到说明有冲突"""
        if not constraint_strings:
            return SpecifierSet("")
        
        try:
            # 直接合并所有约束
            merged_spec = SpecifierSet(",".join(constraint_strings))
            
            # 分析约束逻辑，检查是否有明显冲突
            specs = list(merged_spec)
            
            # 检查固定版本冲突
            fixed_versions = [spec for spec in specs if spec.operator == "=="]
            if len(fixed_versions) > 1:
                return None  # 多个固定版本就是冲突
            
            # 优先检查固定版本是否被排除
            exclude_specs = [spec for spec in specs if spec.operator == "!="]
            if fixed_versions and exclude_specs:
                for fixed_spec in fixed_versions:
                    for exclude_spec in exclude_specs:
                        if fixed_spec.version == exclude_spec.version:
                            return None  # 固定版本被排除
            
            # 检查范围冲突 - 修正逻辑，严格区分 < 和 <=，> 和 >=
            def max_operator_strictness(spec):
                # < 最严格，其次 <=
                return (Version(spec.version), 0 if spec.operator == '<' else 1)
            def min_operator_strictness(spec):
                # > 最严格，其次 >=
                return (Version(spec.version), 0 if spec.operator == '>' else 1)

            min_specs = [spec for spec in specs if spec.operator in [">=", ">"]]
            max_specs = [spec for spec in specs if spec.operator in ["<=", "<"]]
            
            # 构建范围约束
            range_constraints = []
            if min_specs and max_specs:
                # 找出最严格的最小版本约束
                strictest_min = max(min_specs, key=min_operator_strictness)
                # 找出最严格的最大版本约束  
                strictest_max = min(max_specs, key=max_operator_strictness)
                
                # 检查最严格的最小版本是否满足最严格的最大约束
                if not strictest_max.contains(Version(strictest_min.version)):
                    return None  # 范围冲突
                
                range_constraints = [str(strictest_min), str(strictest_max)]
            elif min_specs:
                strictest_min = max(min_specs, key=min_operator_strictness)
                range_constraints = [str(strictest_min)]
            elif max_specs:
                strictest_max = min(max_specs, key=max_operator_strictness)
                range_constraints = [str(strictest_max)]
            
            # 创建范围SpecifierSet
            range_spec = SpecifierSet(",".join(range_constraints)) if range_constraints else SpecifierSet("")
            
            # 检查固定版本是否在范围内
            if fixed_versions:
                fixed_version = fixed_versions[0]  # 只有一个固定版本
                if not range_spec.contains(Version(fixed_version.version)):
                    return None  # 固定版本不在范围内，冲突
                
                # 固定版本在范围内，返回固定版本+排除约束
                result_constraints = [str(fixed_version)]
            else:
                # 没有固定版本，返回范围约束
                result_constraints = range_constraints
            
            # 添加排除约束
            for exclude_spec in exclude_specs:
                result_constraints.append(str(exclude_spec))
            
            # 返回处理后的SpecifierSet
            return SpecifierSet(",".join(result_constraints)) if result_constraints else SpecifierSet("")
                
        except Exception as e:
            logger.warning(f"合并约束失败: {e}")
            return None

    def resolve_version_conflicts_interactive(self, conflicts):
        """
        交互式解决冲突包，让用户手动选择要安装的版本
        """
        resolved = {}
        for pkg, info in conflicts.items():
            print(f"\n[冲突包] {pkg}")
            print("  版本要求：")
            for i, req in enumerate(info['requirements'], 1):
                print(f"    {i}. {req}")
            
            # 检查是否已安装
            pkg_name_lower = pkg.lower()
            pkg_name_normalized = pkg_name_lower.replace('_', '').replace('-', '')
            
            installed_pkg_name = None
            # 遍历已安装的包，对每个包名进行标准化处理
            for installed_name in self.installed_packages.keys():
                installed_name_normalized = installed_name.lower().replace('_', '').replace('-', '')
                if pkg_name_normalized == installed_name_normalized:
                    installed_pkg_name = installed_name
                    break
            
            if installed_pkg_name:
                print(f"  📦 已安装版本: {self.installed_packages[installed_pkg_name]}")
                print(f"  💡 提示: 该包已安装，但版本要求存在冲突。如果当前版本能满足您的需求，可以跳过安装。")
                
                # 显示建议
                if 'suggestions' in info:
                    print("  建议：")
                    for i, suggestion in enumerate(info['suggestions'], 1):
                        print(f"    {i}. {suggestion}")
                
                while True:
                    print(f"\n请选择 {pkg} 的处理方式：")
                    print("1. 选择上述版本要求中的一个（将重新安装）")
                    print("2. 手动输入版本限制（将重新安装）")
                    print("3. 跳过此包（保持当前已安装版本）")
                    
                    choice = input("请输入选择 [1/2/3]: ").strip()
                    
                    if choice == '1':
                        # 选择版本要求中的一个
                        try:
                            version_choice = input(f"请选择版本要求 [1-{len(info['requirements'])}]: ").strip()
                            if not version_choice:
                                print(f"已跳过 {pkg}")
                                break
                            
                            choice_num = int(version_choice)
                            if choice_num < 1 or choice_num > len(info['requirements']):
                                print("❌ 无效选择，请重新输入")
                                continue
                            
                            selected_req = info['requirements'][choice_num - 1]
                            # 提取版本部分
                            if '==' in selected_req:
                                version_part = selected_req.split('==')[1].strip()
                                resolved[pkg] = f"=={version_part}"
                            elif '>=' in selected_req:
                                version_part = selected_req.split('>=')[1].strip()
                                resolved[pkg] = f">={version_part}"
                            elif '<=' in selected_req:
                                version_part = selected_req.split('<=')[1].strip()
                                resolved[pkg] = f"<={version_part}"
                            elif '!=' in selected_req:
                                version_part = selected_req.split('!=')[1].strip()
                                resolved[pkg] = f"!={version_part}"
                            elif '~=' in selected_req:
                                version_part = selected_req.split('~=')[1].strip()
                                resolved[pkg] = f"~={version_part}"
                            else:
                                resolved[pkg] = selected_req
                            
                            print(f"✅ 已选择: {pkg}{resolved[pkg]}")
                            break
                            
                        except ValueError:
                            print("❌ 无效输入，请重新选择")
                            continue
                    
                    elif choice == '2':
                        # 手动输入版本限制
                        user_input = input(f"请输入你想安装的 {pkg} 版本（如 ==1.2.3 或 >=1.0,<2.0，直接回车跳过）：").strip()
                        if not user_input:
                            print(f"已跳过 {pkg}")
                            break
                        
                        # 简单校验输入
                        try:
                            from packaging.specifiers import SpecifierSet
                            _ = SpecifierSet(user_input)
                            resolved[pkg] = user_input
                            print(f"✅ 已设置: {pkg}{user_input}")
                            break
                        except Exception:
                            print("❌ 输入格式有误，请重新输入（如 ==1.2.3 或 >=1.0,<2.0）")
                            continue
                    
                    elif choice == '3':
                        print(f"已跳过 {pkg}，保持当前已安装版本 {self.installed_packages[installed_pkg_name]}")
                        break
                    
                    else:
                        print("❌ 无效选择，请重新输入")
                        continue
            else:
                # 未安装的情况，显示建议
                if 'suggestions' in info:
                    print("  建议：")
                    for i, suggestion in enumerate(info['suggestions'], 1):
                        print(f"    {i}. {suggestion}")
                
                while True:
                    print(f"\n请选择 {pkg} 的安装方式：")
                    print("1. 选择上述版本要求中的一个")
                    print("2. 手动输入版本限制")
                    print("3. 跳过此包")
                    
                    choice = input("请输入选择 [1/2/3]: ").strip()
                    
                    if choice == '1':
                        # 选择版本要求中的一个
                        try:
                            version_choice = input(f"请选择版本要求 [1-{len(info['requirements'])}]: ").strip()
                            if not version_choice:
                                print(f"已跳过 {pkg}")
                                break
                            
                            choice_num = int(version_choice)
                            if choice_num < 1 or choice_num > len(info['requirements']):
                                print("❌ 无效选择，请重新输入")
                                continue
                            
                            selected_req = info['requirements'][choice_num - 1]
                            # 提取版本部分
                            if '==' in selected_req:
                                version_part = selected_req.split('==')[1].strip()
                                resolved[pkg] = f"=={version_part}"
                            elif '>=' in selected_req:
                                version_part = selected_req.split('>=')[1].strip()
                                resolved[pkg] = f">={version_part}"
                            elif '<=' in selected_req:
                                version_part = selected_req.split('<=')[1].strip()
                                resolved[pkg] = f"<={version_part}"
                            elif '!=' in selected_req:
                                version_part = selected_req.split('!=')[1].strip()
                                resolved[pkg] = f"!={version_part}"
                            elif '~=' in selected_req:
                                version_part = selected_req.split('~=')[1].strip()
                                resolved[pkg] = f"~={version_part}"
                            else:
                                resolved[pkg] = selected_req
                            
                            print(f"✅ 已选择: {pkg}{resolved[pkg]}")
                            break
                            
                        except ValueError:
                            print("❌ 无效输入，请重新选择")
                            continue
                    
                    elif choice == '2':
                        # 手动输入版本限制
                        user_input = input(f"请输入你想安装的 {pkg} 版本（如 ==1.2.3 或 >=1.0,<2.0，直接回车跳过）：").strip()
                        if not user_input:
                            print(f"已跳过 {pkg}")
                            break
                        
                        # 简单校验输入
                        try:
                            from packaging.specifiers import SpecifierSet
                            _ = SpecifierSet(user_input)
                            resolved[pkg] = user_input
                            print(f"✅ 已设置: {pkg}{user_input}")
                            break
                        except Exception:
                            print("❌ 输入格式有误，请重新输入（如 ==1.2.3 或 >=1.0,<2.0）")
                            continue
                    
                    elif choice == '3':
                        print(f"已跳过 {pkg}")
                        break
                    
                    else:
                        print("❌ 无效选择，请重新输入")
                        continue
        
        return resolved


def main():
    print(f"Python 版本: {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}")
    print(f"Python 解释器路径: {sys.executable}")
    print(f"当前工作路径: {os.getcwd()}")

    parser = argparse.ArgumentParser(
        description="智能依赖管理工具",
        formatter_class=argparse.ArgumentDefaultsHelpFormatter
    )
    parser.add_argument(
        'paths',
        nargs='+',
        help="包含requirements.txt的目录路径"
    )
    parser.add_argument(
        '--debug',
        action='store_true',
        help="启用调试输出"
    )

    args = parser.parse_args()

    if args.debug:
        logger.setLevel(logging.DEBUG)

    manager = DependencyManager()

    # 用户选择操作类型
    print("\n请选择操作模式：")
    print("1. 非静默安装 requirements.txt 中的依赖（显示详细进度）")
    print("2. 静默安装 requirements.txt 中的依赖（推荐）")
    print("3. 还原上一次快照（requirements_snapshot.txt）")
    print("4. 查找指定包在哪些requirements文件中定义")
    print("5. 安装开发依赖（black, flake8, pytest等）")
    choice = input("请输入数字 [1/2/3/4/5]：").strip()

    if choice == '1':
        # 非静默安装
        verbose = True
    elif choice == '2':
        # 静默安装
        verbose = False
    elif choice == '3':
        if manager.restore_snapshot():
            return
        else:
            sys.exit(1)
    elif choice == '4':
        # 查找包功能
        package_name = input("\n请输入要查找的包名: ").strip()
        if not package_name:
            print("❌ 包名不能为空")
            return
            
        print(f"\n🔍 正在查找包 '{package_name}' 在以下路径中的定义...")
        for path in args.paths:
            print(f"  - {path}")
        
        results = manager.find_package_in_requirements(package_name, args.paths)
        
        if not results:
            print(f"\n❌ 未找到包 '{package_name}' 在任何requirements文件中的定义")
        else:
            print(f"\n✅ 找到 {len(results)} 个匹配项：")
            for i, result in enumerate(results, 1):
                print(f"\n{i}. 文件: {result['file_path']}")
                print(f"   行号: {result['line_number']}")
                print(f"   包名: {result['package_name']}")
                if result['version_spec']:
                    print(f"   版本: {result['version_spec']}")
                print(f"   原始行: {result['original_line']}")
        return
    elif choice == '5':
        # 安装开发依赖
        print("\n🔧 安装开发依赖...")
        dev_packages = {
            'black': '>=21.0.0',
            'flake8': '>=3.8.0',
            'pytest': '>=6.0.0',
            'pytest-cov': '>=2.0.0',
            'mypy': '>=0.900',
            'isort': '>=5.0.0'
        }
        if not manager._install_package_batch(dev_packages, None, True):
            sys.exit(1)
        return
    else:
        print("❌ 无效选择，使用默认静默安装模式")
        verbose = False

    # 默认执行安装流程
    all_requirements = []
    for path in args.paths:
        all_requirements.extend(manager.get_requirements_from_path(path))

    if not all_requirements:
        logger.warning("未找到任何有效的 requirements.txt")
        return

    # 保存快照
    manager.save_snapshot()
    all_requirements = sorted(all_requirements, key=lambda x: x['req_obj'].name)
    # 解析并安装依赖
    to_install, conflicts = manager.resolve_dependencies(all_requirements)

    if not manager.install_packages(to_install, conflicts, args.paths, verbose):
        sys.exit(1)


if __name__ == '__main__':
    main()
